package week03;

import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.HttpVersion;
import okhttp3.Headers;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author edward
 * @since 2021-07-11
 */
public class HttpOutboundHandler {

    private final String[] servers;
    private final List<HttpFilter> filters = new LinkedList<>();
    private final ThreadPoolExecutor executor;
    private final OkHttpClient okHttpClient;
    private static final AtomicInteger SERVER_ID = new AtomicInteger();

    public HttpOutboundHandler(String... servers) {
        this.servers = servers;

        int cores = Runtime.getRuntime().availableProcessors();
        executor = new ThreadPoolExecutor(cores, cores << 1,
                1, TimeUnit.MINUTES,
                new LinkedBlockingQueue<>(1024));
        executor.allowCoreThreadTimeOut(true);

        okHttpClient = new OkHttpClient.Builder()
                .connectTimeout(Duration.ofSeconds(3))
                .readTimeout(Duration.ofSeconds(20))
                .build();

    }

    public void addFilter(HttpFilter filter) {
        filters.add(filter);
    }

    public void process(FullHttpRequest request, ChannelHandlerContext ctx) {
        executor.execute(() -> httpRequest(request, ctx));
    }

    private void httpRequest(FullHttpRequest request, ChannelHandlerContext ctx) {
        // A -> B -> C
        for (HttpFilter filter : filters) {
            filter.inboundFilter(request, ctx);
        }
//        byte[] body = ("Hello, Netty Http Server" + request.uri()).getBytes(StandardCharsets.UTF_8);
        FullHttpResponse response = null;
        try {
            String server = chooseServer();
            byte[] body;
            Headers headers;
            Request targetRequest = new Request.Builder().url(server + request.uri()).build();
            try (Response targetResponse = okHttpClient.newCall(targetRequest).execute()) {
                headers = targetResponse.headers();
                body = targetResponse.body() != null ? targetResponse.body().bytes() : "No Content".getBytes(StandardCharsets.UTF_8);
            }
            response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
                    Unpooled.wrappedBuffer(body));
            for (String name : headers.names()) {
                response.headers().add(name, headers.get(name));
            }
            response.headers().add("name", "Netty");
            response.headers().add("content-type", "text/plain");
            response.headers().add("content-length", body.length);
            response.headers().add("ProxyServer", server);
        } catch (Exception e) {
            e.printStackTrace();
            byte[] error = "Http Server Error".getBytes(StandardCharsets.UTF_8);
            response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.INTERNAL_SERVER_ERROR,
                    Unpooled.wrappedBuffer(error));
            response.headers().add("content-length", error.length);
        } finally {
            // C -> B -> A
            for (int i = filters.size() - 1; i >= 0; i--) {
                filters.get(i).outboundFilter(response, ctx);
            }
            if (!HttpUtil.isKeepAlive(request)) { // check is http connection: keepalive
                ctx.write(response).addListener(ChannelFutureListener.CLOSE);
            } else {
                ctx.write(response);
            }
            ctx.flush();
        }
    }

    private String chooseServer() {
        // 保证正数
        return servers[(SERVER_ID.getAndIncrement() & Integer.MAX_VALUE) % servers.length];
    }
}
